﻿using SAIM.Models.DAL;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace SAIM.Models.BLL
{
    public class AspNetUsersBLL : IDisposable
    {
        private SAIMEntities db;
        private AspNetUsersDAL _value;
        private string _id = string.Empty;
        private string _userName = string.Empty;

        public enum UserMessage
        {
            UserAdded,
            UserEdited,
            Exception
        }

        /// <summary>
        /// Contiene la información de todos los usuarios en la base datos
        /// </summary>
        public AspNetUsersDAL Value 
        {
            get { return ((_value = _value ?? db.AspNetUsers.FirstOrDefault(u => (u.Id == _id) || (u.UserName == _userName) ))); }
            set { _value = value; }
        }

        /// <summary>
        /// Crea una nueva instancia de la clase AspNetUsersBLL, la cual es utilizada para obtener la información de un usuario
        /// </summary>
        /// <param name="Id">ID de usuario</param>
        /// <param name="UserName">Nombre de usuario</param>
        public AspNetUsersBLL(string Id, string UserName = null)
        {
            db = new SAIMEntities();
            _id = Id;
            _userName = UserName ?? string.Empty;
        }

        /// <summary>
        /// Devuelve un usuario mediante si ID o nombre
        /// </summary>
        /// <param name="RPE">ID de usuario</param>
        /// <param name="Name">Nombre de usuario</param>
        /// <returns></returns>
        public static List<AspNetUsersDAL> FindUser(string RPE, string Name)
        {
            List<AspNetUsersDAL> users;

            using (SAIMEntities dataBase = new SAIMEntities())
            {
                if ((RPE != string.Empty) && (Name != string.Empty)) // Si se recibe ID y nombre usar ambos
                {
                    users = dataBase.AspNetUsers.Where(u =>
                        (u.Id.StartsWith(RPE ?? string.Empty)) && (u.UserName.Contains(Name ?? string.Empty))
                    ).ToList();
                }
                else // De lo contrario solo usa uno de los dos
                {
                    users = dataBase.AspNetUsers.Where(u =>
                        (u.Id.StartsWith(RPE ?? string.Empty)) || (u.UserName.Contains(Name ?? string.Empty))
                    ).ToList();
                }
                return users;
            }
        }

        /// <summary>
        /// Devuelve una lista con nombre y UPN de usuarios.
        /// Implementar en control typeahead para busqueda y autocompletado de usuarios
        /// </summary>
        /// <param name="Name">Nombre de usuario</param>
        /// <returns></returns>
        public static List<UserTypeaheadModel> FindTypeaheadUsers(string Name)
        {
            List<UserTypeaheadModel> typeaheadUsers = new List<UserTypeaheadModel>();
            int count = 1;

            using (SAIMEntities dataBase = new SAIMEntities())
            {
                var users = dataBase.AspNetUsers.Where(u => u.UserName.Contains(Name ?? string.Empty)).ToList();
                foreach (AspNetUsersDAL user in users)
                {
                    // El limite de usuarios a regresar es 5 
                    if ((++count) > 5)
                        break;
                    typeaheadUsers.Add(new UserTypeaheadModel()
                    {
                        UPN = user.Id,
                        Name = user.UserName
                    });
                }    
            }

            return typeaheadUsers;
        }

        /// <summary>
        /// Devuelve el nombre del usuario
        /// </summary>
        /// <returns>Nombre del usuario, de no existir devuelve una cadena vacía</returns>
        public string getUserName()
        {
            return (Value != null ? Value.UserName : string.Empty);
        }

        /// <summary>
        /// Devuelve el email del usuario
        /// </summary>
        /// <returns></returns>
        public string getUserEmail()
        {
            return (Value != null ? Value.Email : string.Empty);
        }

        /// <summary>
        /// Devuelve un valor binario indicando la existencia del usuario
        /// </summary>
        /// <returns></returns>
        public bool Exist()
        {
            return (Value != null ? true : false);
        }

        /// <summary>
        /// Valida que el usuario actual tenga un rol específico
        /// </summary>
        /// <param name="roleName">Rol</param>
        /// <returns>Verdadero si stá en el rol, falso si no está en el rol</returns>
        public bool isInRole(string roleName)
        {
            List<AspNetRolesDAL> rolesByUser;

            if (Value != null)
            {
                // Se obtienen todos los roles del usuario
                if(Value.AspNetProfiles != null)
                {
                    rolesByUser = Value.AspNetRoles.Concat(Value.AspNetProfiles.AspNetRoles).ToList();
                }
                else
                {
                    rolesByUser = Value.AspNetRoles.ToList();
                }
                // Busca el rol dentro de los roles del usuario
                AspNetRolesDAL rol = rolesByUser.FirstOrDefault(r => r.Name == roleName);
                return (rol != null ? true : false);
            }
            return false;
        }

        /// <summary>
        /// Establece un perfil al usuario
        /// </summary>
        /// <param name="_ProfileId">ID de perfil</param>
        /// <returns>Verdadero si se asigna correctamente el prefil, falso de lo contrario</returns>
        public bool setProfile(long _ProfileId)
        {
            try
            {
                Value.ProfileId = _ProfileId;
                db.SaveChanges();
            }
            catch (Exception) { return false; }
            return true;
        }

        /// <summary>
        /// Devuelve mediante roles los tipos de tickets que el usuario puede administrar
        /// </summary>
        /// <returns>Lista de cadenas con todos los roles</returns>
        public List<string> GetRolesForTickets()
        {
            List<AspNetRolesDAL> rolesByUser;

            if (Value != null)
            {
                // Se obtienen todos los roles del usuario
                if (Value.AspNetProfiles != null)
                {
                    rolesByUser = Value.AspNetRoles.Concat(Value.AspNetProfiles.AspNetRoles).ToList();
                }
                else
                {
                    rolesByUser = Value.AspNetRoles.ToList();
                }
                // Encuentra los roles específicos para administración de tickets
                IEnumerable<string> roles = 
                    from r in rolesByUser
                    where (r.ParentRoles != null) && (r.ParentRoles.Name == "Manage tickets")
                    select r.IssueArea;

                return roles.ToList();
            }
            return null;
        }

        /// <summary>
        /// Devuelve el lenguaje que el usuario usa actualmente
        /// </summary>
        /// <returns>Devuelve una cadena especificando el lenguaje</returns>
        public string GetUserLanguage()
        {
            return (Value != null ? Value.Lang : String.Empty);
        }

        /// <summary>
        /// Devuelve todos los usuarios existentes en el sistema
        /// </summary>
        /// <returns>Lista con la información de todos los usuarios en el sistema</returns>
        public List<AspNetUsersDAL> GetAllUsers()
        {
            List<AspNetUsersDAL> users;

            users = db.AspNetUsers.ToList();
            users.Sort((u1, u2) => (u2.Id == Value.Id ? 1 :
                u1.Id.CompareTo(u2.Id)
                ));
            return users;
        }

        public UserMessage UpdateInfo(string userName, string email)
        {
            try
            {
                if (Value == null)
                {
                    AspNetUsersDAL user = new AspNetUsersDAL()
                    {
                        Id = _id != string.Empty ? _id : null,
                        Email = email != string.Empty ? email : null,
                        UserName = userName != string.Empty ? userName : null,
                        ProfileId = 4,
                        Lang = "es-mx"
                    };
                    db.AspNetUsers.Add(user);
                    db.SaveChanges();
                    // Información actualizada correctamente
                    return UserMessage.UserAdded;
                }
                else
                {
                    Value.UserName = userName != string.Empty ? userName : null;
                    Value.Email = email != string.Empty ? email : null;
                    db.SaveChanges();
                    // Información actualizada correctamente
                    return UserMessage.UserEdited;
                }
            }
            catch (Exception)
            {
                // Error al actualizar información
                return UserMessage.Exception;
            }
        }

        public bool IsInArea(string areaName)
        {
            List<AspNetRolesDAL> rolesByUser;

            if (Value != null)
            {
                // Se obtienen todos los roles del usuario
                if (Value.AspNetProfiles != null)
                {
                    rolesByUser = Value.AspNetRoles.Concat(Value.AspNetProfiles.AspNetRoles).ToList();
                }
                else
                {
                    rolesByUser = Value.AspNetRoles.ToList();
                }
                try
                {
                    // Busca el rol correspondiente al área dentro de los roles del usuario
                    AspNetRolesDAL rol = rolesByUser.FirstOrDefault(r => r.Name == db.AspNetRoles.FirstOrDefault(a => a.IssueArea == areaName).Name);
                    return (rol != null ? true : false);
                }
                catch(Exception)
                {
                    return false;
                }
            }
            return false;
        }

        public bool SetRole(string roleName)
        {
            AspNetRolesDAL role = db.AspNetRoles.FirstOrDefault(r => r.Name == roleName);
            AspNetRolesDAL parentRole;

            try
            {
                if (role != null)
                {
                    // Busqueda de rol padre 
                    parentRole = role.ParentRoles;
                    // Mientras existan roles padre se agregan a los roles del usuario
                    while(parentRole != null)
                    {
                        if (!isInRole(parentRole.Name))
                            Value.AspNetRoles.Add(parentRole);
                        parentRole = parentRole.ParentRoles;
                    }
                    // Se agrega el rol inicial
                    if (!isInRole(role.Name))
                        Value.AspNetRoles.Add(role);
                    db.SaveChanges();
                    return true;
                }
                else
                    return false;
            }
            catch (Exception) { return false; }
        }

        public bool RemoveRole(string roleName)
        {
            AspNetRolesDAL role = Value.AspNetRoles.FirstOrDefault(r => r.Name == roleName);
            AspNetRolesDAL parentRole;

            try
            {
                if (role != null)
                {
                    Value.AspNetRoles.Remove(role);
                    // Busqueda de rol padre 
                    parentRole = role.ParentRoles;
                    // Mientras existan roles padre se eliminan de los roles del usuario (siempre que el usuario no cuente con roles hijos al rol).
                    while (parentRole != null)
                    {
                        // Roles de usuario tras eliminar rol
                        List<AspNetRolesDAL> userRoles = GetAllUserRoles();
                        if (userRoles.FirstOrDefault(r => r.ParentRoles != null && (r.ParentRoles.Name == parentRole.Name)) == null && parentRole.Name != "Services")
                        {
                            Value.AspNetRoles.Remove(parentRole);
                            parentRole = parentRole.ParentRoles;
                        }
                        else
                        {
                            parentRole = null;
                        }
                    }
                    db.SaveChanges();
                    return true;
                }
                else
                    return false;
            }
            catch (Exception) { return false; }
        }

        /// <summary>
        /// Devuelve todos los roles de este usuario
        /// (Roles Aplicables a acciones en el sistema)
        /// </summary>
        /// <returns></returns>
        public List<AspNetRoles> GetUserRoles()
        {
            List<AspNetRolesDAL> rolesDAL = Value.AspNetRoles
                .Where(r => (r.HasActionLink != null) && (r.HasActionLink.Value))
                .Union(
                    Value.AspNetProfiles.AspNetRoles
                    .Where(r => (r.HasActionLink != null) && (r.HasActionLink.Value))
                    ).ToList();
            List<AspNetRoles> roles = new List<AspNetRoles>();
            int contador = 0;
            foreach(AspNetRolesDAL roleDAL in rolesDAL)
            {
                roles.Add(new AspNetRoles() 
                { 
                    Id = roleDAL.Id,
                    Name = SAIM.Resources.Site.ResourceManager.GetString(roleDAL.Name.Replace(" ", "")),                   
                    RealName = roleDAL.Name
                });
                if (roles[contador].Name == null)//Si no se le asigno un nombre desde el Site.resource asignar el nombre real,
                    //Esto para el role de servicio social , se puede optimizar.
                {
                    roles[contador].Name = roles[contador].RealName;
                }
                contador++;
            }
            return roles;
        }

        /// <summary>
        /// Devuelve todos los roles de este usuario
        /// </summary>
        /// <returns></returns>
        public List<AspNetRolesDAL> GetAllUserRoles()
        {
            List<AspNetRolesDAL> rolesDAL = Value.AspNetRoles.ToList();
            
            return rolesDAL;
        }

        /// <summary>
        /// Dispose this user.
        /// </summary>
        public void Dispose()
        {
            db.Dispose();
        }
    }
}